ATOM Documentation

← Back to App

# Microsoft Teams Integration - Manual Testing Guide

## Prerequisites

### 1. Environment Variables

Set these in your .env file or Fly.io dashboard:

MICROSOFT_CLIENT_ID=your-client-id-from-azure
MICROSOFT_CLIENT_SECRET=your-client-secret-from-azure
MICROSOFT_TENANT_ID=common  # or your organization's tenant ID
MICROSOFT_REDIRECT_URI=https://atom-saas.fly.dev/api/integrations/callback

### 2. Tools Needed

- **Postman** or **Insomnia** (recommended)

- Or **curl** for command-line testing

- **Microsoft Teams** account with admin access

- Web browser

### 3. Get Your Auth Token

For testing API routes that require authentication, you'll need:

1. Log into your app

2. Open browser DevTools → Application → Cookies

3. Find the session cookie

4. Or use the session token from localStorage

---

## Test 1: Health Check Endpoint

### Purpose

Verify Teams integration is configured and check connection status.

### Test 1.1: Check Configuration (No Auth Required)

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/health"

**Expected Response (Not Configured):**

{
  "status": "unhealthy",
  "configured": false,
  "ok": false,
  "details": {
    "error": "Missing Microsoft credentials",
    "required_env_vars": [
      "MICROSOFT_CLIENT_ID",
      "MICROSOFT_CLIENT_SECRET",
      "MICROSOFT_TENANT_ID",
      "MICROSOFT_REDIRECT_URI"
    ]
  }
}

**Expected Response (Configured but not connected):**

{
  "status": "healthy",
  "configured": true,
  "ok": false,
  "details": {
    "connected": false,
    "provider": "teams",
    "has_credentials": true,
    "message": "Teams integration is configured. Please log in and connect."
  }
}

**Expected Response (Connected):**

{
  "status": "healthy",
  "configured": true,
  "ok": true,
  "details": {
    "connected": true,
    "provider": "teams",
    "has_credentials": true,
    "has_token": true,
    "token_valid": true,
    "user_info": {
      "displayName": "Your Name"
    }
  }
}

---

## Test 2: OAuth Authorization Flow

### Purpose

Test the OAuth authorization URL generation and complete the flow.

### Test 2.1: Generate Authorization URL

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/authorize?tenant_id=YOUR_TENANT_ID" \
  -H "Content-Type: application/json"

**Or via POST:**

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/authorize" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "YOUR_TENANT_ID"
  }'

**Expected Response:**

{
  "success": true,
  "authorization_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...",
  "authUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...",
  "state": "BASE64_ENCODED_STATE_STRING",
  "provider": "teams",
  "scopes": [
    "User.Read",
    "Team.ReadBasic.All",
    "Channel.ReadBasic.All",
    "ChannelMessage.Read.All",
    "Chat.Read",
    "Chat.ReadWrite",
    "OnlineMeetings.ReadWrite",
    "Presence.Read",
    "offline_access"
  ]
}

### Test 2.2: Complete OAuth Flow

1. **Copy the authorization_url from the response**

2. **Open it in your browser**

3. **Sign in with your Microsoft account** (if not already signed in)

4. **Grant permissions** when prompted - you should see:

- Read your teams and channels

- Read and send messages

- Access your presence info

- etc.

5. **After granting, you'll be redirected to**:

```

https://atom-saas.fly.dev/api/integrations/callback?code=AUTHORIZATION_CODE&state=STATE_VALUE

```

6. **If successful, you'll be redirected to**:

```

https://atom-saas.fly.dev/integrations?success=teams

```

### Test 2.3: Verify Connection

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/health"

**Expected Response:**

{
  "status": "healthy",
  "configured": true,
  "ok": true,
  "details": {
    "connected": true,
    "has_token": true
  }
}

---

## Test 3: List Teams Channels

### Purpose

Test fetching channels from a Microsoft Team.

### Test 3.1: Get Your Team ID

First, you need to find your Team ID in Microsoft Teams:

1. Open Microsoft Teams

2. Go to a team

3. Click on "..." next to team name → "Get link to team"

4. The URL will be like: https://teams.microsoft.com/l/team/19:TEAM_ID@thread.tacv2/

5. Copy the TEAM_ID part

### Test 3.2: Fetch Channels

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=YOUR_TEAM_ID" \
  -H "Cookie: your-session-cookie"

**Expected Response:**

{
  "success": true,
  "channels": [
    {
      "id": "19:CHANNEL_ID@thread.tacv2",
      "name": "General",
      "description": "General discussion for the team",
      "type": "standard",
      "createdDateTime": "2024-01-01T00:00:00Z",
      "webUrl": "https://teams.microsoft.com/l/channel/19:CHANNEL_ID/General"
    },
    {
      "id": "19:CHANNEL_ID2@thread.tacv2",
      "name": "Random",
      "description": "Random stuff",
      "type": "private",
      "createdDateTime": "2024-01-02T00:00:00Z",
      "webUrl": "https://teams.microsoft.com/l/channel/19:CHANNEL_ID2/Random"
    }
  ],
  "total": 2
}

### Test 3.3: Test Error Cases

**Missing workspace_id:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels"

Expected: 400 Bad Request with error about missing workspace_id

**Not authenticated:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=xxx"

Expected: 401 Unauthorized with error about tenant ID

---

## Test 4: Send Message to Channel

### Purpose

Test sending messages to a Teams channel.

### Test 4.1: Send a Simple Message

**Request:**

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "workspace_id": "YOUR_TEAM_ID",
    "channel_id": "YOUR_CHANNEL_ID",
    "text": "Hello from the API! This is a test message."
  }'

**Expected Response:**

{
  "success": true,
  "message_id": "MESSAGE_ID",
  "channel_id": "YOUR_CHANNEL_ID",
  "team_id": "YOUR_TEAM_ID",
  "webUrl": "https://teams.microsoft.com/l/channel/MESSAGE_ID"
}

### Test 4.2: Send Message with Importance

**Request:**

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "workspace_id": "YOUR_TEAM_ID",
    "channel_id": "YOUR_CHANNEL_ID",
    "text": "This is an urgent message!",
    "importance": "high"
  }'

### Test 4.3: Send HTML Formatted Message

**Request:**

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "workspace_id": "YOUR_TEAM_ID",
    "channel_id": "YOUR_CHANNEL_ID",
    "text": "<div><b>Bold text</b> and <i>italic text</i></div>"
  }'

### Test 4.4: Reply to Existing Thread

First, get a message_id from the history endpoint, then:

**Request:**

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "workspace_id": "YOUR_TEAM_ID",
    "channel_id": "YOUR_CHANNEL_ID",
    "thread_id": "PARENT_MESSAGE_ID",
    "text": "This is a reply to the thread."
  }'

### Test 4.5: Verify in Teams

1. Open Microsoft Teams

2. Go to the channel

3. Verify the message appears

4. Check formatting and importance

---

## Test 5: Fetch Message History

### Purpose

Test fetching historical messages from a channel.

### Test 5.1: Get Recent Messages (Default Limit: 50)

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/history/YOUR_CHANNEL_ID?workspace_id=YOUR_TEAM_ID" \
  -H "Cookie: your-session-cookie"

**Expected Response:**

{
  "success": true,
  "messages": [
    {
      "id": "MESSAGE_ID",
      "ts": "2024-04-21T10:30:00Z",
      "timestamp": "2024-04-21T10:30:00Z",
      "content": {
        "body": "<div><div><div><div>Message content here</div></div></div></div>"
      },
      "text": "Message content here",
      "direction": "inbound",
      "bot_id": null,
      "status": "delivered",
      "sender": {
        "id": "USER_ID",
        "name": "John Doe",
        "email": "john@example.com"
      },
      "reply_to_id": null,
      "channel_id": "YOUR_CHANNEL_ID",
      "team_id": "YOUR_TEAM_ID"
    }
  ],
  "total": 50,
  "has_more": true,
  "next_link": "https://graph.microsoft.com/v1.0/..."
}

### Test 5.2: Get Specific Number of Messages

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/history/YOUR_CHANNEL_ID?workspace_id=YOUR_TEAM_ID&limit=10" \
  -H "Cookie: your-session-cookie"

### Test 5.3: Pagination Test

If has_more: true, you can fetch more (note: next_link is for reference, you'd implement full pagination in production):

# For now, increase limit or the API handles the first page

---

## Test 6: Analytics Endpoint

### Purpose

Test fetching analytics data for Teams integration.

### Test 6.1: Get All-Time Analytics

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/analytics" \
  -H "Cookie: your-session-cookie"

**Expected Response:**

{
  "success": true,
  "analytics": {
    "message_statistics": [
      {
        "direction": "inbound",
        "count": 150
      },
      {
        "direction": "outbound",
        "count": 45
      }
    ],
    "conversation_statistics": {
      "total_conversations": 25,
      "active_conversations": 12
    },
    "period": {
      "start_date": null,
      "end_date": null
    }
  }
}

### Test 6.2: Get Date-Ranged Analytics

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/analytics?start_date=2024-04-01&end_date=2024-04-21" \
  -H "Cookie: your-session-cookie"

---

## Test 7: Disconnect Integration

### Purpose

Test disconnecting the Teams integration.

### Test 7.1: Disconnect

**Request:**

curl -X DELETE "https://atom-saas.fly.dev/api/v1/integrations/teams/disconnect" \
  -H "Cookie: your-session-cookie"

**Expected Response:**

{
  "success": true,
  "message": "Microsoft Teams disconnected successfully",
  "deleted_tokens": 1
}

### Test 7.2: Verify Disconnection

**Request:**

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/health"

**Expected Response:**

{
  "status": "unhealthy",
  "configured": true,
  "ok": false,
  "details": {
    "connected": false
  }
}

---

## Test 8: Token Refresh (Advanced)

### Purpose

Test automatic token refresh when tokens expire.

### Test 8.1: Simulate Expired Token

1. Go to your database (NeonDB console)

2. Find the integration_tokens row for your tenant and provider='teams'

3. Manually update expires_at to a past date:

```sql

UPDATE integration_tokens

SET expires_at = NOW() - INTERVAL '1 day'

WHERE tenant_id = 'YOUR_TENANT_ID' AND provider = 'teams';

```

### Test 8.2: Make an API Request

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=YOUR_TEAM_ID" \
  -H "Cookie: your-session-cookie"

**Expected Behavior:**

- The API should automatically refresh the token

- The request should succeed

- Check server logs for [Teams Channels] Token refreshed successfully

---

## Test 9: Error Handling Tests

### Test 9.1: Invalid Team ID

curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=invalid-team-id" \
  -H "Cookie: your-session-cookie"

Expected: 403 Forbidden or error from Microsoft Graph API

### Test 9.2: Invalid Channel ID

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "workspace_id": "YOUR_TEAM_ID",
    "channel_id": "invalid-channel-id",
    "text": "Test"
  }'

Expected: 404 Not Found or error from Microsoft Graph API

### Test 9.3: Missing Required Fields

curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
  -H "Content-Type: application/json" \
  -H "Cookie: your-session-cookie" \
  -d '{
    "workspace_id": "YOUR_TEAM_ID"
  }'

Expected: 400 Bad Request with error about missing fields

---

## Test 10: Security & Tenant Isolation

### Purpose

Verify tenant isolation works correctly.

### Test 10.1: Cross-Tenant Test (Optional, if you have multiple tenants)

1. Connect Teams integration with Tenant A

2. Try to access channels using Tenant B's credentials

3. Verify you cannot access Tenant A's channels

### Test 10.2: Verify Database Query Filtering

1. Open database console

2. Run this query:

```sql

SELECT tenant_id, provider, access_token IS NOT NULL as has_token

FROM integration_tokens

WHERE provider = 'teams';

```

3. Verify tokens are properly separated by tenant_id

---

## Postman Collection Example

Save this as Teams Integration.postman_collection.json:

{
  "info": {
    "name": "Teams Integration",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://atom-saas.fly.dev"
    },
    {
      "key": "tenant_id",
      "value": "YOUR_TENANT_ID"
    },
    {
      "key": "team_id",
      "value": "YOUR_TEAM_ID"
    },
    {
      "key": "channel_id",
      "value": "YOUR_CHANNEL_ID"
    }
  ],
  "item": [
    {
      "name": "Health Check",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/integrations/teams/health"
      }
    },
    {
      "name": "Get Authorization URL",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/integrations/teams/authorize?tenant_id={{tenant_id}}"
      }
    },
    {
      "name": "List Channels",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/integrations/teams/channels?workspace_id={{team_id}}"
      }
    },
    {
      "name": "Send Message",
      "request": {
        "method": "POST",
        "url": "{{base_url}}/api/v1/integrations/teams/message",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"workspace_id\": \"{{team_id}}\",\n  \"channel_id\": \"{{channel_id}}\",\n  \"text\": \"Test message from Postman\"\n}"
        }
      }
    },
    {
      "name": "Get Message History",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/integrations/teams/history/{{channel_id}}?workspace_id={{team_id}}&limit=10"
      }
    },
    {
      "name": "Get Analytics",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/integrations/teams/analytics"
      }
    },
    {
      "name": "Disconnect",
      "request": {
        "method": "DELETE",
        "url": "{{base_url}}/api/v1/integrations/teams/disconnect"
      }
    }
  ]
}

---

## Troubleshooting

### Issue: "Tenant ID required"

**Solution:** Make sure you're logged in and include your session cookie in requests.

### Issue: "Teams not connected"

**Solution:** Run the OAuth authorize flow first to connect your Teams account.

### Issue: "Token expired"

**Solution:** The token refresh should happen automatically. If not, disconnect and reconnect.

### Issue: "Missing Microsoft credentials"

**Solution:** Set up the required environment variables (MICROSOFT_CLIENT_ID, etc.)

### Issue: "Access denied / Forbidden"

**Solution:** Check that your Microsoft app has the required permissions and the Team ID is correct.

---

## Testing Checklist

Print this checklist and mark each item as you test:

### Configuration

- [ ] Environment variables are set

- [ ] Health check returns "configured: true"

### OAuth Flow

- [ ] Authorization URL generates correctly

- [ ] Can sign in to Microsoft

- [ ] Permissions are requested and granted

- [ ] Redirect to success page happens

- [ ] Tokens are stored in database

### Channels

- [ ] Can list channels from a team

- [ ] Channel data includes all expected fields

- [ ] Works with multiple teams

### Messages

- [ ] Can send a simple message

- [ ] Message appears in Teams

- [ ] Can send HTML formatted message

- [ ] Can set importance level

- [ ] Can reply to existing thread

### History

- [ ] Can fetch message history

- [ ] Messages are in correct order (newest first)

- [ ] Limit parameter works

- [ ] Sender information is included

### Analytics

- [ ] Can fetch analytics

- [ ] Message statistics are correct

- [ ] Date range filtering works

### Disconnect

- [ ] Can disconnect integration

- [ ] Tokens are removed from database

- [ ] Cannot make API calls after disconnect

### Token Refresh

- [ ] Expired token is automatically refreshed

- [ ] API call succeeds after refresh

### Security

- [ ] Cannot access another tenant's data

- [ ] Tenant ID is properly filtered in queries

- [ ] Error messages don't leak sensitive data

---

## Notes

- All API endpoints require authentication (session-based or header)

- The integration uses Microsoft Graph API v1.0

- Tokens are stored in the integration_tokens table

- OAuth states are stored in the oauth_states table with 10-minute expiry

- Token refresh happens automatically when tokens expire

Happy Testing! 🚀